package jasypt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class AesUtils {
    private static final String EncryptAlg = "AES";

    private static final String Cipher_Mode = "AES/ECB/PKCS7Padding";

    private static final String Encode = "UTF-8";

    private static final int Secret_Key_Size = 16;

    private static final String Key_Encode = "UTF-8";


    /**
     * 获取签名
     *
     * @param map       :时间戳、几个业务数据 这些非签名数据
     * @param keySecret 密钥
     * @return
     */
    public static String getSignature(Map map, String keySecret) throws Exception {
        TreeMap treeMap = JSONObject.parseObject(JSONObject.toJSONString(map), TreeMap.class);//利用json来将Map转换为TreeMap
        //去掉值为非签名字段中是null的，并拼接字符串，获取signatureStr
        String signatureStr = keySecret;
        Set keySet = treeMap.keySet();
        for (Object o : keySet) {
            signatureStr += o + "" + treeMap.get(o).toString();
        }
        signatureStr += keySecret;

        //对signatureStr摘要算法（不可逆算法）加密为信息摘要digest
        String digest = DigestUtils.md5Hex(signatureStr.getBytes()).toUpperCase();

        //对称加密生成签名
        String signature = aesPKCS7PaddingEncrypt(digest, keySecret);

        return signature;
    }


    /**
     * 对称加密业务数据
     *
     * @param data       :业务数据
     * @param keySecret ：密钥
     * @return
     */

    public static String getEncryptedData(Object data, String keySecret) throws Exception {
        String json =null;
        if (data instanceof Map){
            TreeMap treeMap = JSONObject.parseObject(JSONObject.toJSONString(data), TreeMap.class);//会自动讲将值为null的清空
            json = JSON.toJSONString(treeMap);
        }else {
            json = JSON.toJSONString(data);
        }
        String enData = AesUtils.aesPKCS7PaddingEncrypt(json, keySecret);
        return enData;
    }

    /**
     * 对加密的业务数据解密
     *
     * @param data      加密的业务数据
     * @param keySecret :密钥
     * @return
     */
    public static Map getDecryptedData(String data, String keySecret) throws Exception {
        String jsonData = aesPKCS7PaddingDecrypt(data, keySecret);
        return JSONObject.parseObject(jsonData, Map.class);
    }


    /**
     * 签名验证
     *
     * @param map       ：包括签名在内的所有数据加密数据 signature、timeStamp、data
     * @param keySecret :密钥
     * @return
     */
    public static boolean check(Map map, String keySecret) throws Exception {
        String signature = (String) map.get("signature");
        long timeStamp = (long) map.get("timeStamp");
        String data = (String) map.get("data");
        Object tenantId = map.get("tenantId");
        Object appId = map.get("appId");
        //生成 Signature
        String jsonData = AesUtils.aesPKCS7PaddingDecrypt(data, keySecret);

        Map signMap;
        try {
            signMap = JSONObject.parseObject(jsonData, Map.class);
        } catch (Exception e){
            e.printStackTrace();
            signMap=new HashMap();
            signMap.put("data",jsonData);
        }
        signMap.put("timeStamp", timeStamp);
        signMap.put("tenantId",tenantId);
        signMap.put("appId",appId);

        String newSignature = getSignature(signMap, keySecret);

        //对比两个signature
        boolean isSafe = signature.equals(newSignature);
        return isSafe;
    }

    /**
     * AES/ECB/PKCS7Padding 加密
     *
     * @param content
     * @param key     密钥
     * @return aes加密后 转base64
     * @throws Exception
     */
    public static String aesPKCS7PaddingEncrypt(String content, String key) throws Exception {
        try {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

            Cipher cipher = Cipher.getInstance(Cipher_Mode);
            byte[] realKey = getSecretKey(key);
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(realKey, EncryptAlg));
            byte[] data = cipher.doFinal(content.getBytes(Encode));
            String result = new Base64().encodeToString(data);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("AES加密失败：content=" + content + " key=" + key);
        }
    }

    /**
     * AES/ECB/PKCS7Padding 解密
     *
     * @param content
     * @param key     密钥
     * @return 先转base64 再解密
     * @throws Exception
     */
    public static String aesPKCS7PaddingDecrypt(String content, String key) throws Exception {
        try {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

            byte[] decodeBytes = Base64.decodeBase64(content);

            Cipher cipher = Cipher.getInstance(Cipher_Mode);
            byte[] realKey = getSecretKey(key);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(realKey, EncryptAlg));
            byte[] realBytes = cipher.doFinal(decodeBytes);

            return new String(realBytes, Encode);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("AES解密失败" + e.fillInStackTrace(), e);
        }
    }

    /**
     * 对密钥key进行处理：如密钥长度不够位数的则 以指定paddingChar 进行填充；
     * 此处用空格字符填充，也可以 0 填充，具体可根据实际项目需求做变更
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] getSecretKey(String key) throws Exception {
        final byte paddingChar = 0;

        byte[] realKey = new byte[Secret_Key_Size];
        byte[] byteKey = key.getBytes(Key_Encode);
        for (int i = 0; i < realKey.length; i++) {
            if (i < byteKey.length) {
                realKey[i] = byteKey[i];
            } else {
                realKey[i] = paddingChar;
            }
        }

        return realKey;
    }
}
